# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import imp
import logging
import os
import socket
import sys
import traceback

from errno import ENOENT

import gvn.hooks.info


DEFAULT_MESSAGE = 'Unknown hook failure; contact your administrator for help.'

# XXX(epg): I'm not sure about this; 
# If we do have any local logging, it should be somehow configurable,
# defaulting to something in /var/log (or maybe just log to
# repo_dir/hooks/log, and let admins symlink that where they want, or
# use a named pipe.
DEFAULT_LOG_FILE = '/var/tmp/svn.log'

_logger = None
def SetupLogging(logger_name, stream=None, logfile=None, level=logging.INFO):
  """Configure the global logging features.

  """
  global _logger
  if _logger is not None:
    return

  _logger = logging.getLogger(logger_name)
  _logger.setLevel(level)

  if stream is not None:
    stderr_handler = logging.StreamHandler(stream)
    stderr_handler.setFormatter(logging.Formatter('%(message)s'))
    _logger.addHandler(stderr_handler)

  if logfile is not None:
    file_format = logging.Formatter(
        '%(asctime)s %(name)s %(levelname)-8s %(message)s',
        '%a %d %b %Y %H:%M:%S'
        )
    file_handler = logging.FileHandler(logfile, 'a')
    file_handler.setFormatter(file_format)
    _logger.addHandler(file_handler)

def DEBUG(message):
  if _logger is not None:
    _logger.debug(message)

def INFO(message):
  if _logger is not None:
    _logger.info(message)

def WARN(message):
  if _logger is not None:
    _logger.warning(message)


def ImportHook(hook_dir, hook_name):
  """Import the hook module in path and return its RunHook function.
  """
  # Strip off the .py suffix.
  hook_name = hook_name[:-3]
  (fp, path, desc) = imp.find_module(hook_name, [hook_dir])
  return imp.load_module(hook_name, fp, path, desc).RunHook


def ListHooks(hook_info):
  """Return asciibetically sorted list of hooks in hook_info.hook_dir.

  Raises:
  whatever os.listdir would
  """
  return sorted(x for x in os.listdir(hook_info.hook_dir) if x.endswith('.py'))

def main(argv, print_output=True):
  '''print_output can be turned off for unittests.'''

  if len(argv) >= 2 and argv[1] == '--invoked-as':
    argv = argv[2:]

  hook_info = gvn.hooks.info.HookInfo(argv, sys.stdin)

  SetupLogging(hook_info.logger_name(), None, DEFAULT_LOG_FILE)

  try:
    try:
      fp = open(hook_info.repos_path + '/conf/gvn/rejection-message')
    except IOError, e:
      if e.errno != ENOENT:
        raise
      # No rejection-message file => use default.
      message = DEFAULT_MESSAGE
    else:
      # We have the rejection file => use it.
      message = fp.read()
      fp.close()
  except:
    # Any exception other than the rejection-message file not existing
    # => log the traceback and deny the commit with the default message.
    WARN(traceback.format_exc())
    sys.stderr.write("%s: %s" % (socket.gethostname(), DEFAULT_MESSAGE))
    return 4

  # It is important to know which host the error is on so that the admin can
  # read the right logs and not have to guess which host to read from
  message = "%s: %s" % (socket.gethostname(), message)

  try:
    try:
      for path in ListHooks(hook_info):
        try:
          run_hook = ImportHook(hook_info.hook_dir, path)
        except:
          WARN("failed to load %s/%s\n" % (hook_info.hook_dir, path))
          raise
        logger_name = hook_info.logger_name(path)
        logger = logging.getLogger(logger_name)
        hookret = run_hook(hook_info, logger)
        if hookret == -1:
          # Succeed immediately.
          return 0
        if hookret == 0:
          # Pass, keep checking.
          continue
        # Failure of some kind.
        if not isinstance(hookret, basestring):
          WARN("hook module return not 0, -1, or string: '%s'" % (hookret,))
          hookret = message
        if print_output:
          INFO(hookret)
          sys.stderr.write(hookret)
        return 2
    except Exception, e:
      WARN(traceback.format_exc())
      sys.stderr.write(message)
      return 3
    # All hook modules passed.
    return 0
  finally:
    logging.shutdown()
